#ifndef MARKMAKERPLUGIN_MATLAB_INTERFACE2_HPP
#define MARKMAKERPLUGIN_MATLAB_INTERFACE2_HPP
#include "../cpp_plugin_interface.hpp"
#include <opencv2/opencv.hpp>
#include <utility>

#if defined(__cplusplus) && !defined(mclmcrrt_h) && defined(__linux__)
#  pragma implementation "mclmcrrt.h"
#endif
#include "mclmcrrt.h"
#include "mclcppclass.h"

void mwArray2Mat(mwArray &src, cv::Mat &dst) {
    auto dims = src.GetDimensions();
    if (src.NumberOfDimensions() == 3) {
        dst = cv::Mat(dims.Get(1, 1), dims.Get(1, 2), CV_8UC3);
    } else {
        dst = cv::Mat(dims.Get(1, 1), dims.Get(1, 2), CV_8U);
    }

    std::vector<uchar> buff(dst.rows * dst.cols * dst.channels());

    src.GetData(buff.data(), buff.size());

    auto *pbuff = buff.data();
    auto *pdst = dst.data;
    for (int chnnl = 0; chnnl < dst.channels(); ++chnnl) {
        for (int c = 0; c < dst.cols; ++c) {
            for (int r = 0; r < dst.rows; ++r) {
                *(pdst + chnnl + c * dst.channels() + r * dst.cols * dst.channels())
                        = *(pbuff + r + c * dst.rows + chnnl * dst.rows * dst.cols);
            }
        }
    }
}
void Mat2mwArray(cv::Mat &src, mwArray &dst) {
    if (src.channels() == 4)
        cv::cvtColor(src, src, cv::ColorConversionCodes::COLOR_BGRA2GRAY);
    if (!src.isContinuous())
        src = src.clone();
    if (src.channels() > 1) {
        const mwSize inInDims[] = {(mwSize) src.rows, (mwSize) src.cols, (mwSize) src.channels()};
        dst = mwArray(3, inInDims, mxUINT8_CLASS);
    } else {
        const mwSize inInDims[] = {(mwSize) src.rows, (mwSize) src.cols};
        dst = mwArray(2, inInDims, mxUINT8_CLASS);
    }

    std::vector<uchar> buff(src.rows * src.cols * src.channels());

    auto *pbuff = buff.data();
    auto *psrc = src.data;
    for (int chnnl = 0; chnnl < src.channels(); ++chnnl) {
        for (int c = 0; c < src.cols; ++c) {
            for (int r = 0; r < src.rows; ++r) {
                *(pbuff + r + c * src.rows + chnnl * src.rows * src.cols)
                        = *(psrc + chnnl + c * src.channels() + r * src.cols * src.channels());
            }
        }
    }
    dst.SetData(buff.data(), buff.size());
}

void String2MwArray(const std::string &src, mwArray &dst) {
    std::vector<double> data;
    std::stringstream ss(src);
    double num;
    while (ss >> num) {
        data.push_back(num);
    }
    if (data.size() == 0)
        data.push_back(0);
    dst = mwArray(1, data.size(), mxDOUBLE_CLASS);
    dst.SetData(data.data(), data.size());
}

class matlab_interface_t : public cpp_plugin::interface_t {
public:
    using Init_t = bool (*)();
    using Termi_t = void (*)();
    using Run_t = void (*)(int, mwArray &, const mwArray &, const mwArray &);
    matlab_interface_t(Init_t Init, Termi_t Termi, Run_t Run, std::string name, std::string help, std::string json)
            : m_Init(Init), m_Termi(Termi), m_Run(Run), m_name(std::move(name)), m_help(std::move(help)), m_description_json(std::move(json)) {
        m_Init();
    }
    ~matlab_interface_t() override {
        m_Termi();
    }
    std::string get_name() override {
        return m_name;
    }

    std::string get_help_info() override {
        return m_help;
    }
    cv::Mat process(cv::Mat data, std::string args) override {
        if (data.empty())
            return data;
        if (data.channels() != 1 && data.channels() != 3)
            return data;

        mwArray inIn;
        Mat2mwArray(data, inIn);
        mwArray inIn2;
        String2MwArray(args, inIn2);
        mwArray outOut;
        try {
            m_Run(1, outOut, inIn, inIn2);
        }
        catch (const mwException &e) {
            std::cerr << e.what() << std::endl;
        }
        catch (...) {
            std::cerr << "Unexpected error thrown" << std::endl;
        }

        cv::Mat rtv;
        mwArray2Mat(outOut, rtv);
        return rtv;
    }
    std::string get_description_json() override {
        return m_description_json;
    }

private:
    Init_t m_Init;
    Termi_t m_Termi;
    Run_t m_Run;
    std::string m_name;
    std::string m_help;
    std::string m_description_json;
};
#endif //MARKMAKERPLUGIN_MATLAB_INTERFACE2_HPP
